Preprocesamiento de datos#
La limpieza de datos es un paso crítico en el desarrollo de modelos de machine learning, ya que garantiza que los datos utilizados para entrenar y evaluar los modelos sean precisos, confiables y representativos, lo que a su vez mejora la calidad y la eficacia de los modelos resultantes.
import pandas as pd
from numpy import NaN
import numpy as np
import requests
import plotly.express as px
import os
df = pd.read_csv("Data/raw_data.csv",low_memory=False)
df.head()
| Unnamed: 0 | departamento | municipio | codigo_dane | armas_medios | fecha_hecho | genero | grupo_etario | cantidad | |
|---|---|---|---|---|---|---|---|---|---|
| 0 | 299006 | ANTIOQUIA | AMAGÁ | 5030000 | ARMA BLANCA / CORTOPUNZANTE | 2019-01-01 | FEMENINO | ADULTOS | 1 |
| 1 | 299007 | ANTIOQUIA | EL SANTUARIO | 5697000 | ARMA BLANCA / CORTOPUNZANTE | 2019-01-01 | FEMENINO | ADULTOS | 1 |
| 2 | 299008 | ANTIOQUIA | MEDELLÍN (CT) | 5001000 | ARMA BLANCA / CORTOPUNZANTE | 2019-01-01 | MASCULINO | ADULTOS | 2 |
| 3 | 299009 | ANTIOQUIA | NARIÑO | 5483000 | ARMA BLANCA / CORTOPUNZANTE | 2019-01-01 | FEMENINO | ADULTOS | 1 |
| 4 | 299010 | ATLÁNTICO | BARRANQUILLA (CT) | 8001000 | ARMA BLANCA / CORTOPUNZANTE | 2019-01-01 | FEMENINO | ADULTOS | 3 |
Ajustes para columnas, valores nulos y duplicados
df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 276715 entries, 0 to 276714
Data columns (total 9 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 Unnamed: 0 276715 non-null int64
1 departamento 276715 non-null object
2 municipio 276715 non-null object
3 codigo_dane 276715 non-null object
4 armas_medios 276715 non-null object
5 fecha_hecho 276715 non-null object
6 genero 276715 non-null object
7 grupo_etario 275104 non-null object
8 cantidad 276715 non-null int64
dtypes: int64(2), object(7)
memory usage: 19.0+ MB
Eliminamos la columna Codigo_dane, ya que representa el identificador del caso registrado. No es útil para el análisis.
df = df.drop(['codigo_dane','Unnamed: 0'],axis=1)
Tratamiento de Datos Faltantes y Atípicos#
df.isnull().sum()
departamento 0
municipio 0
armas_medios 0
fecha_hecho 0
genero 0
grupo_etario 1611
cantidad 0
dtype: int64
fig = px.box(df, x = 'grupo_etario', y = 'cantidad', title="Cantidad de Casos por Grupo Etario", notched = False)
fig.show()
Este gráfico muestra la distribución de los casos por grupos etarios (adultos, adolescentes, menores y no reportados). Lo más notable es el pico extremadamente alto en la categoría “ADULTOS”, que indica que este grupo representa una proporción muy significativa de los casos totales en comparación con los demás grupos. En contraste, los grupos de “ADOLESCENTES”, “MENORES” y “NO REPORTADO” tienen valores mucho más bajos, sugiriendo que tienen una presencia mucho menor en el total de casos.
fig = px.box(df, x = 'genero', y = 'cantidad', title="Cantidad de Casos por Género", notched = True)
fig.show()
fig = px.box(df, x = 'armas_medios', y = 'cantidad',title= "Cantidad de Casos por Tipo de Arma" ,notched = True)
fig.show()
fig = px.box(df, x = 'departamento', y = 'cantidad',title= "Cantidad de Casos por Departamento" ,notched = True)
fig.show()
# Se reemplaza 'NO REPORTA' con valores NaN
df.replace(['NO REPORTA', 'NO REPORTADO', '-'], np.nan, inplace=True)
empty_info = df.isnull().sum()*100/df.shape[0]
print(f'{empty_info}\n\n{df.isnull().sum()}')
departamento 0.001084
municipio 0.001084
armas_medios 18.749616
fecha_hecho 0.000000
genero 0.104440
grupo_etario 0.684459
cantidad 0.000000
dtype: float64
departamento 3
municipio 3
armas_medios 51883
fecha_hecho 0
genero 289
grupo_etario 1894
cantidad 0
dtype: int64
Imputación de valores faltantes con la moda#
# Imputar con la moda
df['grupo_etario'] = df['grupo_etario'].fillna(df['grupo_etario'].mode()[0])
df['departamento'] = df['departamento'].fillna(df['departamento'].mode()[0])
df['municipio'] = df['municipio'].fillna(df['municipio'].mode()[0])
df['armas_medios'] = df['armas_medios'].fillna('Unknown')
df['genero'] = df['genero'].fillna(df['genero'].mode()[0])
# Validación de cambios
print(df.isnull().sum())
departamento 0
municipio 0
armas_medios 0
fecha_hecho 0
genero 0
grupo_etario 0
cantidad 0
dtype: int64
df = df.loc[:, ~df.columns.duplicated()].copy()
df['departamento'] = df['departamento'].str.normalize('NFKD').str.encode('ascii', errors='ignore').str.decode('utf-8')
df['departamento'] = df['departamento'].replace({'SAN ANDRES':'ARCHIPIELAGO DE SAN ANDRES PROVIDENCIA Y SANTA CATALINA',
'VALLE':'VALLE DEL CAUCA',
'NARINO':'NARIÑO',
'GUAJIRA':'LA GUAJIRA'
})
df.departamento.unique()
array(['ANTIOQUIA', 'ATLANTICO', 'BOLIVAR', 'BOYACA', 'CAUCA', 'CESAR',
'CORDOBA', 'CUNDINAMARCA', 'HUILA', 'MAGDALENA', 'NARIÑO',
'NORTE DE SANTANDER', 'RISARALDA', 'SANTANDER', 'SUCRE',
'VALLE DEL CAUCA', 'CALDAS', 'CAQUETA', 'CASANARE', 'GUAINIA',
'LA GUAJIRA', 'META', 'PUTUMAYO', 'QUINDIO', 'TOLIMA', 'VAUPES',
'ARAUCA', 'CHOCO',
'ARCHIPIELAGO DE SAN ANDRES PROVIDENCIA Y SANTA CATALINA',
'GUAVIARE', 'VICHADA', 'AMAZONAS'], dtype=object)
df.loc[df['municipio'] == 'BOGOTÁ D.C. (CT)', 'departamento'] = 'SANTAFE DE BOGOTA D.C'
new_categories = {
'ARMA BLANCA / CORTOPUNZANTE': 'ARMA BLANCA',
'CORTOPUNZANTES': 'ARMA BLANCA',
'CORTANTES': 'ARMA BLANCA',
'CONTUNDENTES': 'ARMA BLANCA',
'PUNZANTES': 'ARMA BLANCA',
'Unknown': 'DESCONOCIDA'
}
df['armas_medios'] = df['armas_medios'].replace(new_categories)
print(df['armas_medios'].unique(),
df['genero'].unique(),
df['grupo_etario'].unique())
['ARMA BLANCA' 'DESCONOCIDA' 'SIN EMPLEO DE ARMAS' 'ARMA DE FUEGO'] ['FEMENINO' 'MASCULINO'] ['ADULTOS' 'ADOLESCENTES' 'MENORES']
df['departamento'] = df['departamento'].astype('category')
df['municipio'] = df['municipio'].astype('category')
df['genero'] = df['genero'].astype('category')
df['grupo_etario'] = df['grupo_etario'].astype('category')
df['armas_medios'] = df['armas_medios'].astype('category')
df['fecha_hecho'] = pd.to_datetime(df['fecha_hecho'])
df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 276715 entries, 0 to 276714
Data columns (total 7 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 departamento 276715 non-null category
1 municipio 276715 non-null category
2 armas_medios 276715 non-null category
3 fecha_hecho 276715 non-null datetime64[ns]
4 genero 276715 non-null category
5 grupo_etario 276715 non-null category
6 cantidad 276715 non-null int64
dtypes: category(5), datetime64[ns](1), int64(1)
memory usage: 5.8 MB
df[['cantidad']].query('cantidad > 20').count()
df = df.query('cantidad < 20').reset_index(drop=True).copy()
df.to_parquet(os.path.join("Data",'data_cleaned.parquet'), index=None)
df.to_csv(os.path.join("Data",'data_cleaned.csv'), index=None)